package resml

import (
	"crypto"
	"encoding/base64"
	"encoding/hex"
	"errors"
	"fmt"
	"io"
	"strings"
)

// 生成器：必须是 3 字节长度的值

const (
	smd5 = "md5" // MD5
	s1   = "sh1" // SHA1：160 位
	s256 = "sh2" // SHA-256
	s512 = "sh5" // SHA-512
	s224 = "sh4" // SHA-224
	s384 = "sh3" // SHA-384
)

// 编码器：必须是 3 字节长度的值

const (
	c64 = "b64" // URL 安全的 Base64 编码，末尾无 "=" 填充
	c16 = "hex" // 十六进制编码
)

// 已定义的错误

var (
	ErrEtagHashNotSupported  = errors.New("unsupported etag hash")  // 不支持的 ETag 哈希算法
	ErrEtagCoderNotSupported = errors.New("unsupported etag coder") // 不支持的 ETag 编码器
	ErrEtagInvalidString     = errors.New("invalid etag string")    // 无效的 ETag 字符串
)

// 已支持的哈希算法函数
var etag_hashes = map[string]crypto.Hash{
	smd5: crypto.MD5,
	s1:   crypto.SHA1,
	s256: crypto.SHA256,
	s512: crypto.SHA512,
	s224: crypto.SHA224,
	s384: crypto.SHA384,
}

// 根据名称查找 hash 算法
func ETagHashOf(name string) (h crypto.Hash, found bool) {
	h, found = etag_hashes[name]
	return
}

// ETag 编码解码接口
type ETagCoder interface {
	EncodeToString([]byte) string
	DecodeString(string) ([]byte, error)
}

// ETag 编码解码接口的十六进制实现
type etagHexCoder struct{}

func (c *etagHexCoder) EncodeToString(v []byte) string        { return hex.EncodeToString(v) }
func (c *etagHexCoder) DecodeString(v string) ([]byte, error) { return hex.DecodeString(v) }

// 支持的 ETag 编码解码器
var etag_coders = map[string]ETagCoder{
	c64: base64.RawURLEncoding, // URL 安全的 Base64 编码
	c16: &etagHexCoder{},       // 十六进制编码
}

// 按名称判断 ETag 编码器是否支持
func ETagCoderSupported(name string) bool {
	_, exists := etag_coders[name]
	return exists
}

type ETager interface {
	// 从 reader 读取并计算 ETag
	OfReader(r io.Reader) (ETag, error)
	// 从 Reader 中读取内容并写入到 Writer 和内部的 hash.Hash 中，
	// 由 hash.Hash 生成 ETag 的原始值，并使用内部的 encoder 进行编码。
	// 适用于一边复制，一边计算 ETag 的情况。
	OfCopy(w io.Writer, r io.Reader) (ETag, int64, error)
}

type etager struct {
	method  string
	coder   string
	hasher  crypto.Hash
	encoder ETagCoder
}

func (g *etager) OfReader(r io.Reader) (et ETag, err error) {
	et, _, err = g.OfCopy(io.Discard, r)
	return
}

func (g *etager) OfCopy(w io.Writer, r io.Reader) (et ETag, n int64, err error) {
	if r == nil {
		return nil, 0, errors.New("[ETagOfCopy] etag reader is nil")
	}
	hw := g.hasher.New()
	var mw io.Writer
	if w != nil {
		mw = io.MultiWriter(hw, w)
	} else {
		mw = hw
	}
	n, err = io.Copy(mw, r)
	if err != nil {
		return nil, n, err
	}
	sv := fmt.Sprintf("%s%s%s", g.method, g.coder, g.encoder.EncodeToString(hw.Sum(nil)))
	return &etag{str: sv}, n, nil
}

func newETager(m, c string) *etager {
	return &etager{method: m, coder: c, hasher: etag_hashes[m], encoder: etag_coders[c]}
}

// 根据指定的 method 与 coder 创建一个 ETager
func NewETager(method, coder string) (ETager, error) {
	g := &etager{method: method, coder: coder}
	if h, exists := etag_hashes[method]; exists && h > 0 {
		g.hasher = h
	} else {
		return nil, ErrEtagHashNotSupported
	}
	if c, exists := etag_coders[coder]; exists && c != nil {
		g.encoder = c
	} else {
		return nil, ErrEtagCoderNotSupported
	}
	return g, nil
}

// 创建一个使用 Sha256 计算与 hex 编码的 ETag 生成器
func NewSha256HexEtager() ETager {
	return newETager(s256, c16)
}

// 创建一个使用 Sha256 计算与 base64 编码的 ETag 生成器
func NewSha256B64Etager() ETager {
	return newETager(s256, c64)
}

// 资源的 ETag
type ETag interface {
	Method() string // Hash 的方法名称
	Coder() string  // 编码解码器的名称
	Hash() string   // Hash 的已经编码的值
	String() string // 返回资源的 ETag 的字符串表示，由 Coder() 方法生成的编码后的值。可来比较是否相等。
}

// 资源的 ETag 的实现
type etag struct {
	str string
}

// 实现 ETag 接口
func (et *etag) Method() string {
	if et == nil || len(et.str) < 3 {
		return ""
	}
	return et.str[:3]
}
func (et *etag) Coder() string {
	if et == nil || len(et.str) < 6 {
		return ""
	}
	return et.str[3:6]
}
func (et *etag) Hash() string {
	if et == nil || len(et.str) <= 6 {
		return ""
	}
	return et.str[6:]
}
func (et *etag) String() string {
	if et == nil {
		return ""
	}
	return et.str
}

// 解析 ETag 字符串
func ETagParse(str string) (ETag, error) {
	str = strings.Trim(strings.TrimSpace(str), "\"")
	if len(str) < 6 {
		return nil, ErrEtagInvalidString
	}
	m, c, v := str[:3], str[3:6], str[6:]
	if g, err := NewETager(m, c); err != nil {
		return nil, err
	} else if gg, ok := g.(*etager); ok {
		if _, err := gg.encoder.DecodeString(v); err != nil {
			return nil, err
		}
	}
	return &etag{str: str}, nil
}

// 判断两个 ETag 是否相等
func ETagEquals(a, b ETag) bool {
	if a == nil || b == nil || a == b {
		return a == b
	}
	return a.String() == b.String()
}
